/*
 * Copyright (c) 2018. paascloud.net All Rights Reserved.
 * 项目名称：paascloud快速搭建企业级分布式微服务平台
 * 类名称：LogAspect.java
 * 创建人：刘兆明
 * 联系方式：paascloud.net@gmail.com
 * 开源地址: https://github.com/paascloud
 * 博客地址: http://blog.paascloud.net
 * 项目官网: http://paascloud.net
 */

package com.hyacinth.web.aspect;


import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.hyacinth.annotation.LogAnnotation;
import com.hyacinth.annotation.NoNeedAccessAuthentication;
import com.hyacinth.dto.OperationLogDto;
import com.hyacinth.security.SecurityUser;
import com.hyacinth.util.JacksonUtil;
import com.hyacinth.util.RequestUtil;
import com.hyacinth.wapper.WrapMapper;
import com.hyacinth.wapper.Wrapper;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 日志切面
 */
@Slf4j
@Aspect
@Component
public class LogAspect {

    private ThreadLocal<Date> threadLocal = new ThreadLocal<>();

    @Value(value = "${yunpi.saveLogUrl}")
    private String saveLogUrl;

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private TaskExecutor taskExecutor;

    private static final int MAX_SIZE = 2000;

    /**
     * Log annotation.
     */
    @Pointcut("@annotation(com.hyacinth.annotation.LogAnnotation)")
    public void logAnnotation() {
    }

    /**
     * Do before.
     */
    @Before("logAnnotation()")
    public void doBefore() {
        this.threadLocal.set(new Date(System.currentTimeMillis()));
    }

    /**
     * Do after.
     *
     * @param joinPoint   the join point
     * @param returnValue the return value
     */
    @AfterReturning(pointcut = "logAnnotation()", returning = "returnValue")
    public void doAfter(final JoinPoint joinPoint, final Object returnValue) throws Exception {
        this.handleLog(joinPoint, returnValue);
    }

    private void handleLog(final JoinPoint joinPoint, final Object result) {
        final Date startTime = this.threadLocal.get();
        final Date endTime = new Date(System.currentTimeMillis());
        HttpServletRequest request = RequestUtil.getRequest();
        // 获取客服端
        final UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        String requestURI = request.getRequestURI();
        try {
            // 获取日志注解
            LogAnnotation logAnnotation = getLogAnnotation(joinPoint);
            if (logAnnotation == null) {
                return;
            }
            //获取客户端操作系统
            final String os = userAgent.getOperatingSystem().getName();
            //获取客户端浏览器
            final String browser = userAgent.getBrowser().getName();
            final String ipAddress = RequestUtil.getRemoteAddr(request);
            // 创建日志实体
            OperationLogDto opLog = new OperationLogDto();
            opLog.setClassName(joinPoint.getTarget().getClass().getName());
            opLog.setMethodName(joinPoint.getSignature().getName());
            opLog.setRequestUrl(requestURI);
            opLog.setCreatedTime(new Date());
            opLog.setIp(ipAddress);
            opLog.setOs(os);
            opLog.setBrowser(browser);

            opLog.setDelayTime(endTime.getTime() - startTime.getTime());
            opLog.setStartTime(startTime);
            opLog.setEndTime(endTime);

            // 注解解析
            parseLogAnnotation(logAnnotation, opLog, result, joinPoint);
            threadLocal.remove();
            taskExecutor.execute(() -> this.restTemplate.postForObject(saveLogUrl, opLog, Integer.class));
        } catch (Exception ex) {
            log.error("获取注解类出现异常={}", ex.getMessage(), ex);
        }
    }

    /**
     * 校验注解
     *
     * @param annotation 注解
     * @param opLog      日志传输对象
     * @param result     响应结果
     * @param joinPoint  切面对象
     */
    private void parseLogAnnotation(LogAnnotation annotation, OperationLogDto opLog, Object result, JoinPoint joinPoint) {
        // 是否需要验证授权
        NoNeedAccessAuthentication noNeedAuthAnnotation = getNoNeedAuthAnnotation(joinPoint);
        if (noNeedAuthAnnotation == null) {
            SecurityUser loginUser = RequestUtil.getLoginUser();
            opLog.setCreator(loginUser.getUsername());
            opLog.setCreatorCode(loginUser.getCode());
            opLog.setLastOperator(loginUser.getUsername());
            opLog.setLastOperatorCode(loginUser.getCode());
            opLog.setAuthCode(StringUtils.join(loginUser.getRoles().toArray(), ","));
        }
        // 是否保存请求数据
        if (annotation.isSaveReqData()) {
            setRequestData(opLog, joinPoint);
        }
        // 是否保存响应数据
        if (annotation.isSaveRespData()) {
            setResponseData(opLog, result);
        }
        opLog.setLogType(annotation.logType().getType());
        opLog.setLogName(annotation.logType().getName());
        opLog.setDesc(annotation.desc());
    }

    /**
     * 设置响应数据
     */
    private void setResponseData(OperationLogDto requestLog, Object result) {
        try {
            if (result instanceof Wrapper) {
                Wrapper wrapper = (Wrapper) result;
                requestLog.setResponseData(String.valueOf(wrapper));
                requestLog.setRetCode(wrapper.getCode());
                requestLog.setRetMsg(wrapper.getMessage());
            }
        } catch (Exception e) {
            log.error("获取响应数据,出现错误={}", e.getMessage(), e);
        }
    }

    /**
     * 设置请求数据
     */
    private void setRequestData(OperationLogDto uacLog, JoinPoint joinPoint) {
        try {
            Object[] args = joinPoint.getArgs();
            if (args.length == 0) {
                return;
            }
            Object[] parameter = new Object[args.length];
            int index = 0;
            for (Object object : parameter) {
                if (object instanceof HttpServletRequest) {
                    continue;
                }
                parameter[index] = object;
                index++;
            }
            String requestData = JacksonUtil.toJsonWithFormat(parameter);
            if (requestData.length() > MAX_SIZE) {
                requestData = requestData.substring(MAX_SIZE);
            }
            uacLog.setRequestData(requestData);
        } catch (Exception e) {
            log.error("获取响应数据,出现错误={}", e.getMessage(), e);
        }
    }

    /**
     * 是否存在日志注解, 如果存在就记录日志
     */
    private static LogAnnotation getLogAnnotation(JoinPoint joinPoint) {
        Method[] methods = joinPoint.getTarget().getClass().getDeclaredMethods();
        String methodName = joinPoint.getSignature().getName();
        if (null != methods && 0 < methods.length) {
            for (Method met : methods) {
                LogAnnotation relog = met.getAnnotation(LogAnnotation.class);
                if (null != relog && methodName.equals(met.getName())) {
                    return relog;
                }
            }
        }
        return null;
    }

    /**
     * 获取无需授权注解
     */
    private static NoNeedAccessAuthentication getNoNeedAuthAnnotation(JoinPoint joinPoint) {
        Method[] methods = joinPoint.getTarget().getClass().getDeclaredMethods();
        String methodName = joinPoint.getSignature().getName();
        if (null != methods && 0 < methods.length) {
            for (Method met : methods) {
                NoNeedAccessAuthentication noNeedAuthAnnotation = met.getAnnotation(NoNeedAccessAuthentication.class);
                if (null != noNeedAuthAnnotation && methodName.equals(met.getName())) {
                    return noNeedAuthAnnotation;
                }
            }
        }
        return null;
    }

    public static void main(String[] args) {
        List<String> list = Lists.newArrayList("1","2","3");
        String join = StringUtils.join(list.toArray(), ",");
        System.out.println(join);
    }

}
